﻿/* * Created by SharpDevelop.
 * User: Corrado Mio
 * Date: 27/06/2012
 * Time: 20:31
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */
using System;
using System.Collections.Generic;
using System.Threading;

namespace CSharpExt.Collections
{
    
    /// <summary>
    /// 
    /// </summary>
    public class BufferPool
    {
        #region Private Data
        
        private String _name = "BufferPool";
        
        private int _maxCount;
        
        private int _allocTimeout = 1000;
        
        
        private Queue<ByteBuffer> _pool = new Queue<ByteBuffer>();
        
        private int _count;
        
        private int _lastClean = Environment.TickCount;
        
        #endregion
        
        #region Constructor
        
        public BufferPool()
        {
            
        }
        
        #endregion
        
        #region Properties
        
        public String Name
        {
            set{ _name = value; }
        }
        
        public int MaxCount
        {
            set { _maxCount = value; }
        }
        
        public int AllocTimeout
        {
            set { _allocTimeout = value; }
        }
        
        public int CleanTimeout
        {
            set { _allocTimeout = value; }
        }
        
        public int Count
        {
            get { return _count; }
        }
        
        #endregion
        
        #region Operations
        
        public ByteBuffer Allocate()
        {
            ClearPool();
            
            return AllocateFromPool();
        }
        
        public ByteBuffer Allocate(int length)
        {
            ByteBuffer mb = Allocate();
            
            mb.EnsureLength(length);
            
            return mb;
        }
        
        internal void Release(ByteBuffer buffer)
        {
            lock (_pool)
            {
                _pool.Enqueue(buffer);
                
                if (_maxCount > 0)
                {
                    --_count;
                    
                    Monitor.Pulse(_pool);
                }
            }
        }
        
        internal void ClearPool()
        {
            lock (_pool)
            {
                if (_maxCount != 0 || Environment.TickCount - _lastClean < _allocTimeout)
                    return;
                
                _pool = new Queue<ByteBuffer>();
            }
        }
        
        internal ByteBuffer AllocateFromPool()
        {
            lock (_pool)
            {
                ByteBuffer buffer;

                // non ci sono limiti sul numero di buffer allocabili
                if (_maxCount == 0)
                {
                    if (_pool.Count > 0)
                        buffer = _pool.Dequeue();
                    else
                        buffer = new ByteBuffer(this);
                }
                else if (_count < _maxCount)
                {
                    if (_pool.Count > 0)
                        buffer = _pool.Dequeue();
                    else
                        buffer = new ByteBuffer(this);
                    
                    ++_count;
                }
                else
                {
                    if (Monitor.Wait(_pool, _allocTimeout))
                    {
                        ++_count;
                        return _pool.Dequeue();
                    }
                    else
                    {
                        String message = String.Format("Unable to Acquire a MutableBuffer from {0} after {1} ms", 
                                                       _name, 
                                                       _allocTimeout);
                        throw new TimeoutException(message);
                    }
                }

                buffer.AddRef();
                
                return buffer;
            }
        }
        
        #endregion
    }
    
    /// <summary>
    /// La classe mette a disposizione un bytearray che puo' screre e leggere.
    /// in scrittura, il buffer viene incrementato automaticamente
    /// </summary>
    public class ByteBuffer
    {
        #region Private Data
        
        private static readonly int MINALLOC = 512;
        private static readonly byte[] EMPTY = new byte[0];
        
        private BufferPool _pool;
        
        private int _refcount;
        
        private byte[] _buffer = EMPTY;
        
        private int _offset;    // Write
        
        private int _count;     // Write
        
        private int _position;  // Read
        
        #endregion
        
        #region Constructor
        
        internal ByteBuffer(BufferPool pool)
        {
            _pool = pool;
            _refcount = 0;
        }
        
        #endregion
        
        #region ReferenceCount
        
        public void AddRef()
        {
            Interlocked.Increment(ref _refcount);
        }
        
        public void Release()
        {
            if (_refcount == 0)
                throw new InvalidOperationException("Buffer already Released");
            
            Interlocked.Decrement(ref _refcount);
            
            if (_refcount > 0)
                return;
            
            _offset = 0;
            _count  = 0;
            _position = 0;
            _pool.Release(this);
        }
        
        #endregion
        
        #region Properties
        
        public byte[] Buffer
        {
            get { return _buffer; }
        }
        
        /// <summary>
        /// Lunghezza totale del buffer
        /// </summary>
        public int Length 
        {
            get { return _buffer.Length; }
        }
        
        public int Offset
        {
            get { return _offset;  }
            set { _offset = value; }
        }
        
        public int Count
        {
            get { return _count;  }
            set { _count = value; }
        }
        
        /// <summary>
        /// End = Offset + Count
        /// </summary>
        public int End
        {
            get { return _offset + _count; }
        }
        
        /// <summary>
        /// Capacity = Length - (Offset + Count)
        /// </summary>
        public int Capacity
        {
            get { return _buffer.Length - (_offset + _count); }
        }
        
        
        public int Position
        {
            get { return _position;  }
            set { _position = value; }
        }
        
        /// <summary>
        /// Availabe = (Offset + Count) - Position
        /// </summary>
        public int Available
        {
            get { return _offset + _count - _position; }
        }
        
        
        #endregion
        
        #region Operations
        
        public void EnsureLength(int length)
        {
            if (length <= _buffer.Length)
                return;
            
            //length = MINALLOC*((length + MINALLOC - 1)/MINALLOC);
            int rest = length % MINALLOC;
            if (rest > 0)
                length = length + (MINALLOC - rest);
            
            byte[] obuffer = _buffer;
            
            _buffer = new byte[length];
            
            System.Buffer.BlockCopy(obuffer, 0, _buffer, 0, obuffer.Length);
        }
        
        public void EnsureCapacity(int capacity)
        {
            if (capacity <= Capacity)
                return;
            
            EnsureLength(_offset + _count + capacity);
        }
        
        #endregion
        
        #region Read / Write
        
        public ByteBuffer WriteByte(byte b)
        {
            EnsureCapacity(1);
            
            _buffer[_offset++] = b;
            
            return this;
        }
        
        public ByteBuffer Write(byte[] buffer, int offset, int count)
        {
            EnsureCapacity(count);
            
            System.Buffer.BlockCopy(buffer, offset, _buffer, _offset + _count, count);
            _count += count;
            
            return this;
        }
        
        public byte ReadByte()
        {
            if (_position > End)
                throw new ArgumentOutOfRangeException(String.Format("Poition {0} > {1} (End)", _position, End));

            return _buffer[_position++];
        }
        
        public ByteBuffer Read(byte[] buffer, int offset, int count)
        {
            if (count > Available)
                throw new ArgumentOutOfRangeException(String.Format("Count {0} > {1} (Available)", count, Available));
            
            System.Buffer.BlockCopy(_buffer, _position, buffer, offset, count);
            _position += count;
            
            return this;
        }
        
        public ByteBuffer InsertByte(byte b, int at)
        {
            
            if (at > _count)
                throw new ArgumentOutOfRangeException(String.Format("at {0} > {1} (count)", at, _count));
            
            EnsureCapacity(1);
            
            System.Buffer.BlockCopy(_buffer, _offset + at, _buffer, _offset + at+1, _count - at);
            _buffer[at] = b;
            _count += 1;
            
            return this;
        }
        
        public ByteBuffer Insert(byte[] buffer, int offset, int count, int at)
        {
            if (at > _count)
                throw new ArgumentOutOfRangeException(String.Format("at {0} > {1} (count)", at, _count));
            
            EnsureCapacity(count);
            System.Buffer.BlockCopy(_buffer, _offset + at, _buffer, _offset + at + _count, _count - at);
            System.Buffer.BlockCopy( buffer,  offset, _buffer, _offset + at, count);
            _count += count;
            
            return this;
        }
        
        #endregion
    }
}
